package com.ac.scum.log.web.filter;

import com.ac.common.core.exception.BaseException;
import com.ac.common.core.result.Result;
import com.ac.scum.log.web.dto.login.LoginDTO;
import com.ac.scum.log.web.model.login.service.JWTUtil;
import com.ac.scum.log.web.utils.user.CurrentUserUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.Jws;
import io.micrometer.common.util.StringUtils;
import jakarta.annotation.Nullable;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.annotation.Order;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.filter.OncePerRequestFilter;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;

@Slf4j
@Order(1)
@Component
@AllArgsConstructor
public class TokenFilter extends OncePerRequestFilter {

    private final RedisTemplate<String, Object> redisTemplate;

    public static final List<String> NoAuthPath = new ArrayList<>() {{
        add("/auth/phoneLogin");
        add("/auth/client/phoneLogin");
        add("/user/register");
        add("/ftpCheck");
        add("/user/registerSMS");
        add("/wechat/portal/*");
        add("/wechat/redirect/*");
        add("/wechat/pay/notify");
        add("/server/getHasAlarmServers/*");
        add("/player/alarm/pushed/bind");

    }};


    @Override
    protected void doFilterInternal(@Nullable HttpServletRequest request, @Nullable HttpServletResponse response, @Nullable FilterChain filterChain) throws ServletException, IOException {
        if (filterChain == null || request == null || response == null) {
            return;
        }
        AntPathMatcher matcher = new AntPathMatcher();
        for (String path : NoAuthPath) {
            if (matcher.match(path, request.getServletPath())) {
                filterChain.doFilter(request, response);
                return;
            }
        }

        String token = request.getHeader("Authorization");
        Result<String> authFailure = Result.failure("auth failure");
        authFailure.code = 401;
        if (token == null || !token.startsWith("Bearer ")) {
            writeResponse(response, authFailure);
            return;
        }
        token = token.substring(7);
        if (Boolean.FALSE.equals(redisTemplate.hasKey(token))) {
            writeResponse(response, authFailure);
            return;
        }
        Jws<Claims> claimsJws;
        try {
            claimsJws = JWTUtil.parseClaim(token);
        } catch (ExpiredJwtException e) {
            writeResponse(response, authFailure);
            return;
        }
        String phone = (String) claimsJws.getPayload().get("phone");
        if (StringUtils.isBlank(phone)) {
            writeResponse(response, authFailure);
            return;
        }


        if (claimsJws.getPayload().getExpiration().before(new Date())) {
            writeResponse(response, authFailure);
            return;
        }
        LoginDTO loginDTO = (LoginDTO) redisTemplate.opsForValue().get(token);
        if (loginDTO == null) {
            writeResponse(response, authFailure);
            return;
        }
        try {
            String currentLoginUserPhone = CurrentUserUtil.getCurrentLoginUserPhone();
            if (!loginDTO.getAdminUser().getPhone().equals(currentLoginUserPhone)) {
                writeResponse(response, authFailure);
                return;
            }
            Long expire = redisTemplate.getExpire("USER::" + currentLoginUserPhone);
            if (expire == null) {
                writeResponse(response, authFailure);
                return;
            }
            if (expire != -1) {
                redisTemplate.expire("USER::" + currentLoginUserPhone, 1, TimeUnit.HOURS);
                redisTemplate.expire(token, 1, TimeUnit.HOURS);
            }
        } catch (BaseException e) {
            throw new RuntimeException(e);
        }

        filterChain.doFilter(request, response);
    }

    public void writeResponse(HttpServletResponse response, Result<String> authFailure) throws IOException {
        response.setContentType("application/json;charset=UTF-8");
        String jsonResult = new ObjectMapper().writeValueAsString(authFailure);
        response.getWriter().write(jsonResult);
        response.getWriter().flush();
        response.setStatus(authFailure.code);
    }
}